<?php

namespace Yxy\YWechat;

use EasyWeChat\Kernel\Exceptions\BadRequestException;
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
use EasyWeChat\Kernel\Exceptions\RuntimeException;
use EasyWeChat\Kernel\HttpClient\Response;
use EasyWeChat\Kernel\Message;
use EasyWeChat\OfficialAccount\Application;
use Exception;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Request;
use Psr\Http\Message\ResponseInterface;
use ReflectionException;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Throwable;

/**
 * 微信公众号
 */
class Off
{
    /**
     * app
     * @var Application
     */
    protected static Application $app;
    /**
     * 配置信息
     * @var array
     */
    protected static array $config;


    /**
     * 初始化app
     * @return void
     * @throws InvalidArgumentException
     */
    protected static function initApp(): void
    {
        $config = config('ywechat.off');
        foreach ($config as $value) {
            if (empty($value)) {
                throw new Exception("请先填写微信公众号配置");
            }
        }
        self::$config = $config;
        self::$app = new Application([
            'app_id' => $config['appid'],
            'secret' => $config['secret'],
            'token' => $config['token'],
            'aes_key' => $config['aes_key'], // 明文模式请勿填写 EncodingAESKey
            'http' => [
                'throw' => true,
            ],
        ]);
    }

    /**
     * 获取app
     * @return Application
     * @throws InvalidArgumentException
     */
    public static function getApp(): Application
    {
        if (empty(self::$app)) {
            self::initApp();
        }
        return self::$app;
    }

    /**
     * 公众号验签 直接返回本方法即可
     * @return ResponseInterface
     * @throws InvalidArgumentException
     * @throws BadRequestException
     * @throws RuntimeException
     * @throws ReflectionException
     * @throws Throwable
     */
    public static function getOffServe(): ResponseInterface
    {
        return self::getApp()->getServer()->serve();
    }

    /**
     * 获取解密信息
     * @return Message
     * @throws BadRequestException
     * @throws InvalidArgumentException
     * @throws ReflectionException
     * @throws RuntimeException
     * @throws Throwable
     */
    public static function getDecryptedMessage(): Message
    {
        return self::getApp()->getServer()->getDecryptedMessage();
    }

    /**
     * 获取公众号用户信息
     * @param string $openid
     * @return mixed
     * @throws ClientExceptionInterface
     * @throws InvalidArgumentException
     * @throws RedirectionExceptionInterface
     * @throws ServerExceptionInterface
     * @throws TransportExceptionInterface
     */
    public static function getUser(string $openid): mixed
    {
        $api = self::getApp()->getClient();
        $response = $api->get('/cgi-bin/user/info', [
            'openid' => $openid,
        ]);

        if ($response->isFailed()) {
            Log::info('获取用户信息出错' . $openid);
        }

        $result = $response->getContent();

        return json_decode($result, true);
    }

    /**
     * 微信公众号关注事件
     * @param callable $callable $callable($openid(openid), $unionid(开放平台唯一标识))
     * @return void
     * @throws BadRequestException
     * @throws ClientExceptionInterface
     * @throws InvalidArgumentException
     * @throws RedirectionExceptionInterface
     * @throws ReflectionException
     * @throws RuntimeException
     * @throws ServerExceptionInterface
     * @throws Throwable
     * @throws TransportExceptionInterface
     */
    public static function followEvent(callable $callable): void
    {
        $message = self::getDecryptedMessage();
        if ($message->MsgType == 'event' && $message->Event == 'subscribe') {
            /*用户openid*/
            $openid = $message->FromUserName;
            /*获取用户*/
            $user = self::getUser($openid);

            $unionid = $user['unionid'] ?? '';
            $openid = $user['openid'];

            $callable($openid, $unionid);
        }
    }

    /**
     * 发送模版消息
     * @param string $toUser
     * @param string $templateId
     * @param array $data
     * @param string $appid
     * @param string $page
     * @return Response|\Symfony\Contracts\HttpClient\ResponseInterface
     * @throws InvalidArgumentException
     * @throws TransportExceptionInterface
     */
    public static function pushMessage(string $toUser, string $templateId, array $data = [], string $appid = "", string $page = ""): \Symfony\Contracts\HttpClient\ResponseInterface|Response
    {
        $param = [
            'touser' => $toUser,
            'template_id' => $templateId,
            'data' => $data,
            'miniprogram' => false,
        ];
        if (!empty($appid)) {
            $param['miniprogram'] = [
                'appid' => $appid,
            ];
        }
        if (!empty($page)) {
            $param['miniprogram']['pagepath'] = $page;
        }
        return self::getApp()->getClient()->postJson('/cgi-bin/message/template/send', $param);
    }

    /**
     * 公众号授权
     * @param string $callback_url 跳转链接
     * @param string $scope 认证方式  默认 snsapi_base
     * @return string
     * @throws InvalidArgumentException
     */
    public static function oauth(string $callback_url, string $scope = 'snsapi_base'): string
    {
        $oauth = self::getApp()->getOAuth();
        return $oauth->scopes([$scope])->redirect('callback_url');
    }

    /**
     * 获取授权后的用户
     * @return array
     * @throws InvalidArgumentException
     */
    public function oauthUser(): array
    {
        $oauth = self::getApp()->getOAuth();
        $user = $oauth->userFromCode(Request::get('code'));
        return $user->toArray();
    }
}